IAC Driver 2
Volume Number: 4
Issue Number: 10
Column Tag: Advanced Mac'ing
IAC Driver, Part II 
By Frank Alviani, Contributing Editor, Paul Snively, Contributing
Inter-Application Communication Under MutiFinder: The Continuing
Saga,
or
Data, Data, Who’s Got the Data?
or
If One Application is the Master and the Other is the Slave, is the IAC
Driver a Slave Driver?
Greetings, ladies and gentlemen, and welcome to yet another episode in the latest
Macintosh development soap opera. In this segment, we ask the crucial question,
“What does it actually take to make this puppy fly?” Luckily, Frank has spent a great
deal of time and effort answering that question to his own satisfaction while I was out
dancing with Shellie and Alice in Boston. (We should all be so lucky, I might add.)
Seriously, here is our attempt at cleaning up some of the mysteries surrounding
how this driver we’ve written actually works. The definitive answer to that whole
question can, of course, only be ascertained by careful examination of the source code,
a herculean task at best.
Rather than put you, gentle reader, through that, here is our distillation of both
what we wrote last month, and what is in the source code that accompanies this article
(which is supposed to be reasonably short, since the code is far larger than anything
MacTutor has ever published before).
Disclaimer!! This version of the IAC Driver is B1!! We have had enough requests
and inquiries to convince us to go ahead and release this early Beta version for people
to experiment with, understanding that revisions are inevitable. THIS DRIVER
UNDOUBTEDLY STILL HAS A FEW BUGS IN IT! Don’t commit all your eggs to the IAC
basket just yet!!
First of all, the driver had to be remarkably transparent (read: drop it in your
System Folder and forget that it exists). For that reason it is a Startup Document (for
you pre-System 6.0 freaks, it’s an INIT file). Basically there’s a ridiculously simple
little INIT in there that opens the DRVR in the system heap, makes sure it’s in the unit
table, and then makes sure that it won’t go away when the INIT file is closed (by
detaching the resource). Peter Helme of Mac DTS kindly provided a small chunk of code
that finds the highest unoccupied slot in the unit table and renumbers the DRVR
resource to ensure it is loaded there.
Once the driver is there, applications are free to check to see if it’s around and,
if it is, to make use of it. The calls were documented last time as to name and function
(sort of), so I’m going to try to focus on some of the not-so-obvious aspects of what
goes on, and why (Frank is actually explaining the “why”; it’s his design, after all).
First of all, understand one important fact: inter-application communication
under MultiFinder (or any other multitasking environment, for that matter) is
neither more nor less than an exercise in memory management. The source
application has to allocate storage for the transfer of data to the target application, and
somehow the target application has to be made aware that the block of memory has
something that it wants. Also, since the source data can be changed in a fairly dynamic
manner relative to the target retrieving the data, you have to keep track of various
versions or “editions” of the data somehow. All of this is the driver’s responsibility.
It allocates and releases the storage (yes, Paul Sydney, it “does” release the storage),
and it tracks editions as necessary.
A second matter to consider during program design is that inter- application
communication is very much like network communication (only without the messy
wires) and therefore similar problems arise, such as timing and coordination
difficulties. If you’ve never dealt with those before, it’s going to prove very stressful
for a while (although the problems aren’t really of the same magnitude).
Specification changes
Since the time the protocol was published in the August 1988 issue of MacTutor,
a few small changes were found to be necessary in the specifications. Here they are:
1) The identifier for data types was changed from a 16-bit integer to the
4-character resource type used by everybody else (no, I don’t recall the good
reason I had for choosing an integer in the first place).
2) The information returned by the Census call for an extent now includes the latest
edition, since that is necessary for the application to be able to determine if it
needs to read that extent or not.
3) When an extent is deleted by its source, the entry is removed from the table
entirely (rather than just setting the edition to -1 as originally stated). Targets
that try to access that extent in the future will get a “No such extent” error from
the driver, and will know to not try to read it anymore (the extent should NOT be
purged from the application’s internal tables, since re-opening the document in
the future will re-establish the dependency).
Internal Data Structures
One of the keys to understanding any piece of code is a good grasp of the data
structures it uses: how they are used and why they were chosen. The data structures
used in the IAC driver are not terribly complex.
The extent table - is the heart of the driver. It is a fixed-length table that
includes information about where an extent originates, who is interested in it, and
what is the latest version of data that each target has read.
Since the entry for an extent is a fixed size structure, the entries for each edition
are kept in a linked list. Editions are added to the list in chronological order, with the
newest edition closest to the extent entry. Each edition in turn is a variable-size
structure (the size of an extent’s data can of course vary, and the number of formats
written to the driver can vary for each edition) and is therefore also a linked list. The
data format identifier for each chunk of data is stored in the block with the data as a
32-bit header.
The interest mask - is used to identify targets for a source extent. The position in
the document-ID table serves as a bit number in this mask (i.e. slot 2 is bit 2 in the
mask). In the extent’s entry in the master table, the bits serve as identifiers of the
target documents, and are only turned off by a target document severing a link. In the
mask stored with each edition, the bits serve to record which documents have not yet
read the data, and are turned off as soon as a target has read an edition. The read
routine checks the interest mask for an edition after returning the data, and deletes the
edition if no interested targets remain.
The document-ID table - is a table of unique document IDs. Every entry in this
table is one of three types - Empty (zero), Target Document (1), or Source Document
(a negative number). It is critical for every document interacting with the IAC driver
to appear in this table, since the slot_ID is used to access the interest mask for extent
entries; yet, document IDs are only assigned to source documents (a document that is
only a target of others does not need to be identified by other than its slot_ID) - how to
resolve this?
Target-only documents are those that do not pass a document ID to the “Complete
Dependency” call; a dummy entry is put into the table to uniquely identify them for
this session, and a slot_ID is assigned. Later, if they make an “Add Dependency” call
and pass the existing slot_ID in (as they must under the protocol), the slot_ID is used
to put the new document ID in the table, replacing the dummy entry. We take advantage
of the fact that all date/times returned by the system are negative numbers to make
this easy to process.
These data structures were designed for simplicity; they contain the minimum
amount of information that will allow the driver to do its job. They do not, for
example, maintain any information about the “boundaries” of extents, since that is
highly application specific and impossible to anticipate; also, maintaining that
material in the driver would result in excessive traffic between the IAC driver and
applications.
A Routine Commentary
Most of the stuff from last time that concentrated on the interface specifications
was explained fairly well, and I don’t wish to copy it here. Instead, I will provide my
commentary on things that were perhaps not that clear. I’ll go routine by routine.
Open - it turns out to be impossible to check for MultiFinder at boot time, when
this driver is opened. Therefore, it is the responsibility of the application to check and
refuse to try and support IAC operations when MultiFinder isn’t running. The ‘C’ glue
code provided does this check during the open call, so you shouldn’t need to.
AddDependency --What’s hard about this? Not much! Basically what it does is to
add a new entry to the driver’s internal tables that are used to keep track of data that is
being made available to interested applications. This is called by the source
application.
There are 2 ways in which a source application can make this call: with a default
document ID of zero, or with existing document and slot IDs (the driver has no way of
checking up on you, and assumes that non-zero IDs are valid). The default case is easy
- just give the document it’s IDs and send it home. If IDs are provided, however, life
becomes complicated - there is the possibility of collisions with slot IDs already
registered. If that occurs, a new slot ID is assigned and returned to the caller - this is
why the slot ID returned from this call MUST ALWAYS be the one used in further
communications with the driver.
A reminder that this call does NOT pass any data to the driver; an explicit “Write
Data” call must be made.
CompleteDependency --This one is called by the “ target” application, not the
source! It tells the driver “hey, I’m interested in whatever’s available right now.” It
basically lets the driver set up the “Interested mask” for the dependency properly.
I’ll say a bit more about the “Interested mask” later.
RemoveDependency --This one is a bit trickier, as it can be called by either the
source application or the target. If it’s called by the source, all editions of the data
that have been posted get totally wiped, and the extent itself is deleted so that any
targets that were interested get informed that the data has been blasted out from under
them (they’ll get a “No such dependency” error). On the other hand, it may be a
single target that’s calling. If that is the case, the appropriate bit in the “Master
Interest mask” (and all editions) gets reset (that’s one less suitor, darling). When no
one cares at all anymore (that is, the entire “Master Interest mask” goes to zero), the
driver will blow away all of the editions just as it would if the source removed the
dependency. Got all that? There’s a quiz next month.
A reminder that this call does NOT retrieve any data from the driver; an explicit
“Read Data” call must be made.
AvailableDependency --I had a truly bad pun for this one, but I’ll resist the
temptation. This is easy. It just makes the extent indicated by the parameters the
source to all future defaulted “CompleteDependency”s until another dependency is
made the available one. This has the highly desirable side-effect of making it trivial to
have more than one target interested in the same source.
Status --Here’s a simple one, too. It just tells whomever is calling how many
documents have been registered with the driver, and how many extents that exist are
relevant to the document asking for the status (have editions that the target hasn’t read
yet). Its real raison d’etre, though, is to tell the caller what version of the driver is
installed (ahem).
Census --is like a high-powered Status. It doesn’t care who’s making the call. It
just blasts out a list of all the extents that it knows of, period.
WriteData --this is rather like using the clipboard. The idea is that you may
want to pass data in more than one format. A spreadsheet, for example, may wish to
pass both the cell selection (let’s call that type CELL) and a pie chart of the selection
(as a PICT). Remember, this call is made by the source after it has established a
dependency. The master interest mask is copied from the extent table to the header for
the new edition to establish which targets have yet to read the new data.
ReadData -- the target’s analog of WriteData, of course. It’s like SFGetFile
inasmuch as it gives a list of preferred data types, in order of desirability. The target
application makes this call, passing the most recent edition number of the data that it
received last time. If there are one or more editions more recent than that, the driver
will return the latest, marking the “Interested mask” for each successive edition as
appropriate. It’s conceivable (in fact, all too likely for some applications, such as
databases) that the target won’t be calling ReadData sufficiently frequently to get each
and every edition that the source application posts, which is why we use editions in the
first place. Reading an edition turns off the target’s bit in the edition-interest-mask,
and all older editions. An interest mask of zero results in that edition being deleted.
Notes on the code
This was developed on a Mac Plus running MPW 2.0.2 under System 6.0 It should
work on any machine running MultiFinder, although the technique used in the glue to
determine if MultiFinder is actually running is not authorized by Apple. Their claim
that “you should only check for services you need” ignores the fact that in this case the
service needed is the multitasking capability. A few other matters...
First, none of the people involved with this project belong to the
“Names-cost-a-dollar-a-letter” school of identifiers. Hence, the verbose identifiers
and extensive comments. It was hard enough to debug with almost every line
commented, believe me!
Second, the INIT resource is set up so that if you have the object to ShowInit by
Paul Mercer, you can modify a couple of lines and link it in to display start-up icons.
We didn’t have permission to print his source, and don’t like publishing software in
MacTutor without every line of source needed to rebuild it, so this seems a workable
compromise.
Third, some of the makefiles may have traces of the Odesta development
environment showing through, even though I tried to “genericize” them. I hope they
won’t be too hard to correct..
Bug Fixes and Updates
Inevitably, bugs will be found in this code, and we already have thought of several
desirable improvements to add in the future (after we’re recovered from this exercise
in hubris). It is not reasonable to expect Dave Smith and MacTutor to act as the central
exchange for this driver, since the effort involved could become significant.
After January 1st, 1989, we will be acting as IAC Central, issuing revised
versions as necessary, etc. Bug reports can be sent to us immediately (we’d like to
think there will never be any, but...) along with suggestions for improvements. We can
be reached by electronic mail (please, no phones calls!!) at the following addresses:
Paul Snively - GEnie: PSNIVELY
(The first line of any letter should be:
ATTN: PSNIVELY PAUL SNIVELY for the mail server he is using)
Frank Alviani - GEnie: TOOLSMITH
Delphi: TOOLSMITH
In addition, US Snail can reach us (slowly) at the following address:
CodeSmiths
P.O. Box 8744
Waukegan, Ill 60079
We will try and respond in a timely fashion, given the constraints of very
demanding jobs, a house renovation, and small children.
That about wraps it up for our end. Here -- at long last!-- is the source code
for the driver and the glue (in ‘C’) to simplify writing applications to support the
protocol!
{1}
Listing: driver.m
# Makefile for the IAC Driver!
# Frank Alviani -- 8/88
ob = {hlxEtc} #Set these definitions to your normal working
directories
sr = {hlxSrc}
prog = {hlx}
e = echo
“{prog}SAWSInit” ï“{ob}SAWSdrvr.lnk” “{ob}SAWSInit”
“{sr}SAWSINIT.r”
{e} ‘Date -a -t‘ Rez IAC >> “{log}”
Rez -t INIT -c SAWS -o “{prog}SAWSInit” “{sr}SAWSinit.r”
“{ob}SAWSDRVR.a.o” ï“{sr}SAWSDRVR.a”
{e} ‘Date -a -t‘ Asm SAWSDRVR.a >> “{log}”
Asm “{sr}SAWSDRVR.a” -i Me:MPW:AIncludes: -o “{hlxetc}”
“{ob}SAWSdrvr.lnk” ï“{ob}SAWSDRVR.a.o”
{e} ‘Date -a -t‘ Link SAWSDRVR.a. >> “{log}”
Link -t INIT -c SAWS -rt DRVR=31 -sg .IAC “{ob}SAWSDRVR.a.o” ∂
-o “{ob}SAWSdrvr.lnk”
“{ob}SAWSINIT.a.o” ï“{sr}SAWSINIT.a”
{e} ‘Date -a -t‘ Asm SAWSINIT.a >> “{log}”
Asm “{sr}SAWSINIT.a” -i Me:MPW:AIncludes: -o “{hlxetc}”
“{ob}SAWSInit” ï“{ob}SAWSINIT.a.o”
{e} ‘Date -a -t‘ Link SAWSINIT.a >> “{log}”
Link -rt INIT=0 -ra =16 “{ob}SAWSINIT.a.o” -o “{ob}SAWSInit”
{2}
Listing: SysEnv.a
;
; Glue for SysEnvirons that was skipped by Apple
; F. Alviani
; 7/88
;
INCLUDE ‘TRAPS.A’ ;Include Memory manager macros.
PRINT ON
SYSENVIRONS FUNC EXPORT
movea.l 4(sp),a0 ;a0 contains ptr to world record
move.w 8(sp),d0 ;d0 contains desired version
_SysEnvirons
movea.l (sp)+,a0 ;get return addr, remove
lea 6(sp),sp ;pop parameters
move d0,(sp) ;put result in place
jmp (a0) ;go home
DC.B ‘SYSENVIR’
END
{3}
Listing: IAC.h
/* This is the set of externs needed to access the IAC interface
routines */
/* The pointer/handle indicators are only reminders */
extern short iac_add_dependency(); /* *doc_ID,
*slot_ID, *hat_check,
*edition */
extern short iac_available_dependency(); /* doc_id, hat_check */
extern short iac_census(); /* *extent_count,
**extent_info */
extern short iac_complete_dependency(); /* *doc_id, *slot_id,
*hat_check */
extern short iac_open();
extern short iac_read_data(); /* doc_id,
slot_id, hat_check, *edition,
fmt_pref[],
*fmt_code, **ext_data */
extern short iac_remove_dependency(); /* doc_id, slot_id,
hat_check */
extern short iac_status(); /* slot_id,
*vers_id, *doc_count,
*extent_count */
extern short iac_write_data(); /* doc_id,
hat_check, *edition,
fmt_count,
**ext_data */
/* error codes for iac_open, etc. */
# define NO_DRIVER -1
# define EARLY_SYS -2
# define MAX_EXTS 64
typedef struct {
long doc_ID;
short hat_check;
short ed_level;
} info_rec;
typedef struct {
info_rec ext_entry[MAX_EXTS];
} info_tbl, *info_tblP, **info_tblH;
{4}
Listing: IAC.c
/***
*
* File: IAC.c
*
* Package: Inter Application Communications
*
* Description: interface package to the driver for the use of
* application programs.
*
* Author(s):
* FEA 6/19/88
*
*/
# include
# include < files.h>
# include
# include
# include
# include
# define MIN_BLK_SIZE 0xC
# define DEBUG false
static short iac_ref_num;
/**
* Routine: iac_open
*
* The IAC driver is opened by this call and the ioRefNum
* saved so it doesn’t need to be passed with all calls.
* A null value is returned if the open is successful;
* otherwise the operating system error is
* returned.
*/
short iac_open()
{
SysEnvRec theWorld;
/* ALMOST complete knowledge about the world */
THz s_ZoneP, a_ZoneP; /* so we can check zone adjacency */
OSErr the_err;
short result = 0;
the_err = SysEnvirons(1, &theWorld);
if (theWorld. systemVersion < 0x0420) /* too early! */
{
result = EARLY_SYS; /* “no multi finder” */
}
else
{
/* check for multifinder active by checking if system zone
is adjacent to application zone, which never happens under
MultiFinder.
*/
s_ZoneP = SystemZone();
a_ZoneP = ApplicZone();
if ( ((long) s_ZoneP->bkLim + MIN_BLK_SIZE) == (long) a_ZoneP )
{
result = EARLY_SYS;
/* zones adjacent - no multifinder */
}
else /* now try to actually open the IAC driver */
{
SysBeep(1);
result = OPENDRIVER(“\p.IAC”, &iac_ref_num);
}
}
return(result);
}
/**
* Routine: iac_add_dependency
*
* This is used to inform the IAC driver that a new
* dependency has been established and should be
*added to its internal tables. A null value is
* returned if the open is successful; otherwise the operating
* system error is returned.
*/
short iac_add_dependency(doc_id, slot_id, hat_check, edition)
long *doc_id;
/* identifies the source document (permanent) */
short *slot_id;
/* identifies the source document (session) */
short *hat_check; /* extent identifier */
short *edition;
/* how many times extent has changed (session) */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short slot_id;
short hat_check;
short edition;
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit testing without MF */
# endif
my_params.func = 1; /* set up private parameter block */
my_params.doc_id = *doc_id;
my_params.slot_id = *slot_id;
my_params.hat_check = *hat_check;
the_blk.ioCompletion = nil;/* setup driver parametr block */
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBWrite(&the_blk.qLink, false);
/* add the dependency */
if (the_err == noErr) /* update output parameters */
{
*doc_id = my_params.doc_id;
*slot_id = my_params.slot_id;
*hat_check = my_params.hat_check;
*edition = my_params.edition;
}
return(the_err);
}
/**
* Routine: iac_complete_dependency
*
* This is used to inform the IAC driver of a document that
* is interested in a particular dependency. A null value is
* returned if the open is successful; otherwise the
* operating system error is returned.
*/
short iac_complete_dependency(doc_id, slot_id, hat_check)
long *doc_id;
/* identifies the source document (permanent) */
short *slot_id;
/* identifies the source document (session) */
short *hat_check; /* extent identifier */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short slot_id;
short hat_check;
} my_params;
# if DEBUG
return(the_err=noErr);
/* short circuit for testing without MF */
# endif
my_params.func = 2; /* set up private parameter block */
my_params.doc_id = *doc_id;
my_params.slot_id = *slot_id;
my_params.hat_check = *hat_check;
the_blk.ioCompletion = nil;/* setup driver parametr block */
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBWrite(&the_blk.qLink, false);
/* complete the dependency */
if (the_err == noErr) /* update output parameters */
{
*doc_id = my_params.doc_id;
*slot_id = my_params.slot_id;
*hat_check = my_params.hat_check;
}
return(the_err);
}
/**
* Routine: iac_remove_dependency
*
* This is used to inform the IAC driver that a document is
* no longer interested in a particular dependency. If the
* document is the original source all “ targets” will be
* informed that there is no longer any such
* extent; if all targets lose interest the source will be
* informed that it no longer needs to update the extent.
*/
short iac_remove_dependency(doc_id, slot_id, hat_check)
long doc_id;
/* identifies the source document (permanent) */
short slot_id;
/* identifies the source document (session) */
short hat_check; /* extent identifier */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short slot_id;
short hat_check;
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit testing without MF */
# endif
my_params.func = 3; /* set up private parameter block */
my_params.doc_id = doc_id;
my_params.slot_id = slot_id;
my_params.hat_check = hat_check;
the_blk.ioCompletion = nil; /* setup driver paramtr block */
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBWrite(&the_blk.qLink, false);
/* remove the dependency */
return(the_err);
}
/**
* Routine: iac_available_dependency
*
* This sets the indicated extent as the “available extent”
*to be used as the source for future defaulted
*”complete_dependency” calls.
*/
short iac_available_dependency(doc_id, hat_check)
long doc_id;/* identifies the source document (permanent) */
short hat_check; /* extent identifier */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short hat_check;
} my_params;
# if DEBUG
return(the_err=noErr);
/* short circuit for testing without MF */
# endif
my_params.func = 4; /* set up private parameter block */
my_params.doc_id = doc_id;
my_params.hat_check = hat_check;
the_blk.ioCompletion = nil;/* setup driver parametr block */
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBWrite(&the_blk.qLink, false);
/* this dependency is now “available” */
return(the_err);
}
/**
* Routine: iac_status
*
* This allows the application program to find out what’s
* going on.
*/
short iac_status(slot_id, vers_id, doc_count, extent_count)
short slot_id; /* identifies the inquiring document (session) */
short *vers_id; /* driver version*100 */
short *doc_count; /* count of active documents */
short *extent_count; /* count of extents relevant to inquiring doc
*/
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
short slot_id;
short vers_id;
short doc_count;
short extent_count;
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit testing without MF */
# endif
my_params.func = 5; /* set up private parameter block */
my_params.slot_id = slot_id;
the_blk.ioCompletion = nil;/* setup driver parametr block */
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBRead(&the_blk.qLink, false);
/* read IAC status */
if (the_err == noErr) /* update output parameters */
{
*vers_id = my_params.vers_id;
*doc_count = my_params.doc_count;
*extent_count = my_params.extent_count;
}
return(the_err);
}
/**
* Routine: iac_census
*
* This provides identifying info for all registered extents.
*/
short iac_census(extent_count, extent_info)
short *extent_count; /* count of extents registered */
info_tblP extent_info; /* Ptr to table of info for each extent */
{
IOParam the_blk;
OSErr the_err;
short i;
struct {
short func;
short extent_count;
info_rec extent_info[MAX_EXTS];
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit for testing without MF */
# endif
my_params.func = 6; /* set up private parameter block */
the_blk.ioCompletion = nil; /* set up driver parameter block
*/
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBRead(&the_blk.qLink, false);
/* read IAC status */
if (the_err == noErr) /* update output parameters */
{
*extent_count = my_params.extent_count;
BlockMove (&my_params.extent_info[0],
(Ptr) extent_info,
(long) my_params.extent_count * sizeof(info_rec));
}
return(the_err);
}
/**
* Routine: iac_write_data
*
* This updates the data for the specified extent, resulting
* in a new change level.
*/
short iac_write_data(doc_id, hat_check, edition, fmt_count, ext_data)
long doc_id; /* identifies source document (permanent) */
short hat_check; /* extent identifier */
short *edition; /* how many times extent has changed (session) */
short fmt_count; /* number of formats being written */
Handle ext_data; /* Handle to actual data */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short hat_check;
short edition;
short fmt_count;
Handle the_dataH;
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit for testing without MF */
# endif
my_params.func = 7; /* set up private parameter block */
my_params.doc_id = doc_id;
my_params.hat_check = hat_check;
my_params.fmt_count = fmt_count;
my_params.the_dataH = ext_data;
the_blk.ioCompletion = nil; /* set up driver parameter block
*/
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);;
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBWrite(&the_blk.qLink, false);
/* update dependency */
if (the_err == noErr) /* update output parameters */
{
*edition = my_params.edition;
}
return(the_err);
}
/**
* Routine: iac_read_data
*
* This is used to retrieve the actual data for the latest
* change_level for the specified extent. The IAC driver will
* record that the inquiring document has read the data.
*
* ext_data will be resized by the driver to hold the data.
*/
short iac_read_data(doc_id, slot_id, hat_check, edition, fmt_pref,
fmt_code, ext_data)
long doc_id; /* identifies the source document (permanent) */
short slot_id; /* identifies the source document (session) */
short hat_check; /* extent identifier */
short *edition; /* how many times extent has changed (session)
*/
long fmt_pref[3]; /* preferred formats, descending desirability */
long *fmt_code; /* format returned to caller */
Handle ext_data; /* Handle to actual data */
{
IOParam the_blk;
OSErr the_err;
struct {
short func;
long doc_id;
short slot_id;
short hat_check;
short edition;
long fmt_pref[3];
long fmt_code;
Handle ext_data;
} my_params;
# if DEBUG
return(the_err=noErr); /* short circuit for testing without MF */
# endif
my_params.func = 8; /* set up private parameter block */
my_params.doc_id = doc_id;
my_params.slot_id = slot_id;
my_params.hat_check = hat_check;
my_params.edition = *edition;
my_params.fmt_pref[0] = fmt_pref[0];
my_params.fmt_pref[1] = fmt_pref[1];
my_params.fmt_pref[2] = fmt_pref[2];
my_params.ext_data = ext_data;
the_blk.ioCompletion = nil; /* set up driver parameter block
*/
the_blk.ioRefNum = iac_ref_num;
the_blk.ioBuffer = &my_params;
the_blk.ioReqCount = sizeof(my_params);
the_blk.ioPosMode = fsFromStart;
the_blk.ioPosOffset = 0;
the_err = PBRead(&the_blk.qLink, false); /* read the data */
if (the_err == noErr)
{
*edition = my_params.edition;
*fmt_code = my_params.fmt_code;
}
return(the_err);
}
{5}
Listing: SAWSDRVR.a
;**************************************************************
;***** The SAWS Inter-Application Communication Driver *****
;***** Written with blazing speed 6-8/88 by Paul F. Snively
;***** With one HECK of a lotta help from Frank Alviani ***
;***** and Mike Walsh *****
;**************************************************************
;
;Modification History:
;6/19/88 First Draft--1.0D1--Paul F. Snively
;7/10/88 Second draft--1.0B1--Paul F. Snively & Michael R. ;Walsh
;8/7/88 Yet another crack at it--1.0B1--Paul F. Snively &
; Frank Alviani
;8/8-21/88 Final Debugging -- Frank Alviani
;
;Basically this puppy is the DRVR resource that actually
;implements Frank’s cool ideas that are documented in the
; article that accompanies this listing.
;
INCLUDE ‘Traps.a’
INCLUDE ‘QuickEqu.a’
INCLUDE ‘SysEqu.a’
INCLUDE ‘SysErr.a’
INCLUDE ‘ToolEqu.a’
STRING PASCAL
NumOfDocs EQU 32 ;Max of 32 documents
NumOfExtents EQU 64
Unimpl EQU $9F ;For testing environment
OSDisp EQU $8F
DummyID EQU 1
NoMoreDocs EQU -2000 ;This one isn’t used--yet
NoMoreSlots EQU -2001 ;Neither is this one--yet
WriteFailed EQU -2002 ;Or this one...
MissingLink EQU -2003;Yet another SAWS-defined error
NoNewerEdition EQU -2004 ;YASAWSE
ReadFailed EQU -2005 ;SASAWSE
NoSuchDep EQU -2006
OldROMs EQU -2007
ExtentRec RECORD 0
DocID DS.L 1;Home document for this extent
HatCheck DS.W 1 ;Unique ID for this extent
Edition DS.W 1 ;The edition number
MstrInterestMsk DS.L 1 ;Master Interest Mask
EditionList DS.L 1 ;Edition List
ExtentSize EQU *
ENDR
EditionHdr RECORD 0
NextEd DS.L 1 ;Handle to next Ed
InterestMask DS.L 1 ;Mask for this particular Ed
NumFormats DS.W 1 ;How many formats stored?
EdInstances DS.L 0 ;Instances start here
EditionSize EQU *
ENDR
IACGlobals RECORD 0
ExtentCount DS.W 1 ;Number of extents extant
ExtentTable DS.L NumOfExtents*ExtentRec.ExtentSize
DocIDs DS.L NumOfDocs;Unique Document identifiers
AvailDependency DS.L 1 ;Offset of currently available dependency
IACGlobalsSize EQU *
ENDR
DriverEntry PROC ; See Device Manager IM:2
IMPORT DriverOpen,DriverPrime
IMPORT DriverCtl,DriverDone
IMPORT DriverClose
DC.B (1<
DC.B 0 ; Lower byte is unused
DC.W 0*60 ; 0 sec periodic update
DC.W 0 ; We don’t do events
DC.W 0 ; No menu for this accessory
DC.W DriverOpen-DriverEntry ; Open routine
DC.W DriverPrime-DriverEntry ; Prime
DC.W DriverCtl-DriverEntry ; Control
DC.W DriverDone-DriverEntry ; Status - unused
DC.W DriverClose-DriverEntry ; Close
DC.B ‘.IAC’ ;The name of our driver
ALIGN 2 ; Word align
ENDPROC
DriverOpen PROC
IMPORT IACGlobalsSize
TST.W ROM85 ;old ROMs?
BGE.S @1 ;no, keep going?
MOVE.W #OldROMs,D0
BRA.S ExitOpen
@1
with IACGlobals
TST.L dCtlStorage(A1) ;Did we already allocate our
space?
BNE.S ExitOpen ;If so, then we’re already open
MOVE.L #IACGLobalsSize,D0 ;How much global space do we
want?
_NewHandle sys,clear ;Try to allocate it
BNE.S ExitOpen ;Fail miserably since error
occurred
@2
MOVE.L A0,dCtlStorage(A1) ;Otherwise store handle
MOVE.W #0,ExtentCount ;We start with zero extents
endwith ;IACGlobals
ExitOpen
RTS ;We’re outta here
ENDPROC
DriverClose PROC
IMPORT WipeExtentData
MOVE.L dCtlStorage(A1),D0 ;Get our global handle
BEQ.S ExitClose ;If we don’t have one, leave!
MOVEA.L D0,A6 ;Otherwise get into address
register
with IACGlobals
MOVE.W ExtentCount(A6),D7 ;How many extents are there?
LEA ExtentTable(A6),A0 ;Point to the ExtentTable
MOVE.L A0,D6 ;Get it in the right register
endwith ;IACGlobals
WalkExtents
MOVEA.L D6,A0 ;Get Extent Pointer
JSR WipeExtentData ;Kill its data
NextExtent
with ExtentRec
SUBQ.W #1,D7 ;Decrement extent counter
BEQ.S EndClose ;And continue until done
ADD.L #ExtentSize,D6 ;Point to next extent
BRA.S WalkExtents ;And loop
endwith ;ExtentRec
EndClose
MOVEA.L dCtlStorage(A1),A0 ;Get our globals handle
_DisposHandle ;And get rid of it
ExitClose
RTS ;Back to caller
ENDPROC
;
; This is the main entry point for ALL functions of the driver
;
DriverPrime PROC
IMPORT DriverAddDependency,DriverCompleteDependency
IMPORT DriverRemoveDependency,DriverRemoveDependency
IMPORT DriverAvailableDependency,DriverStatus
IMPORT DriverCensus,DriverWriteData
IMPORT DriverReadData
MOVEA.L dCtlStorage(A1),A6 ;Get our global handle
MOVEA.L ioBuffer(A0),A4 ;Get the address of the “data”
MOVE.W (A4),D0 ;Get the routine identifier
ADD.W D0,D0 ;Multiply it by two
MOVE.W RoutineTable(D0),D0 ;Get routine offset
JSR RoutineTable(D0) ;Go to the proper routine
MOVE.L JIODone,-(A7) ;wrap up
RTS
RoutineTable
DC.W 0 ;1-based function codes...
DC.W DriverAddDependency-RoutineTable
DC.W DriverCompleteDependency-RoutineTable
DC.W DriverRemoveDependency-RoutineTable
DC.W DriverAvailableDependency-RoutineTable
DC.W DriverStatus-RoutineTable
DC.W DriverCensus-RoutineTable
DC.W DriverWriteData-RoutineTable
DC.W DriverReadData-RoutineTable
ENDPROC
;
; This routine adds the source of a link to the dependency table
;
DriverAddDependency PROC
IMPORT FindSlotID
AddDepRec RECORD 0
Func DS.W 1
docID DS.L 1
slot_ID DS.W 1
hatCheck DS.W 1
edition DS.W 1
AddDepRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Get globals pointer
LEA IACGlobals.DocIDs(A2),A3 ;Point past end of
extentTable
LEA IACGlobals.ExtentTable(A2),A2 ;Point to base of
extent table
MOVEQ #0,D2 ;Clear our counter
AddDepLoop
TST.L ExtentRec.docID(A2) ;Is this slot available?
BEQ.S FoundAvailSlot ;If so, we’re done looking
ADD.L #ExtentRec.ExtentSize,D2 ;Maintain “available”
offset
ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next
record
CMPA.L A2,A3 ;Are we done?
BGT.S AddDepLoop ;If not, look again
AddDepFail
MOVE.W #NoMoreDocs,D0 ;Return custom OSErr
BRA AddDepExit ;And leave
FoundAvailSlot
MOVE.L AddDepRec.docID(A4),D0 ;Get the docID
BNE.S ExistingDocID ;Go if it already exists
MOVE.L Time,D0 ;Else copy it from low RAM
MOVE.L D0,AddDepRec.docID(A4) ;Update param block
ExistingDocID
MOVE.L D0,ExtentRec.docID(A2) ;Store the docID
MOVE.L D0,D5; ;Save for later
MOVE.L A2,D4 ;Save extent-rec addr for
later
MOVE.W AddDepRec.slot_ID(A4),D1 ;Did we get passed a
slot_ID?
BEQ.S NeedASlot ;Go get one if not
MOVE.L (A6),A3
LEA IACGlobals.DocIDS(A3),A3 ;Pt to table of unique
SUBQ.W #1,D1 ;Adjust index to 0-based
LSL.W #2,D1 ;Convert to offset
CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID target
only?
BEQ.S HaveASlot ;Yes, put in real docID
CMP.L 0(A3,D1.W),D5 ;ID in table OK?
BEQ.S HaveASlot ;Yes, use slotID as is
NeedASlot
BSR FindSlotID ;Get a valid slot ID
BLT.S AddDepExit ;No slots - Leave
HaveASlot
MOVE.L D5,0(A3,D1.W) ;Save docID in slot
MOVEA.L (A6),A2 ;Dereference global handle
MOVE.W D0,AddDepRec.slot_ID(A4) ;Store the slot_ID
MOVE.W AddDepRec.hatCheck(A4),D1 ;Is there already a
hatCheck?
BNE.S StuffHatCheckCalc ;If so, bypass calculation
LEA IACGlobals.docIDs(A2),A3 ;Point past the end
LEA IACGlobals.ExtentTable(A2),A2 ;Point to right
place
; Search for highest hatCheck registered for this docID
MOVEQ #0,D1 ;Initialize counter
MOVE.L AddDepRec.docID(A4),D0 ;Get the docID again
HatCheckLoop
CMP.L ExtentRec.docID(A2),D0 ;Is the docID the same?
BNE.S NotSame ;No, ignore
CMP.W ExtentRec.hatCheck(A2),D1 ;Compare hatChecks
BGE.S NotSame ;Go if no need for update
MOVE.W ExtentRec.hatCheck(A2),D1 ;Save max hatCheck
NotSame
ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record
CMPA.L A2,A3 ;Are we done?
BGT.S HatCheckLoop ;Go if not
ADDQ.W #1,D1 ;Bump to next hatCheck
MOVE.W D1,AddDepRec.hatCheck(A4) ;Update param block
StuffHatCheckCalc
MOVEA.L D4,A2 ;Point to “working” extent
MOVE.W D1,ExtentRec.hatCheck(A2) ;Store hatCheck
MOVE.W #0,AddDepRec.edition(A4) ;Pass back edition zero
MOVE.W #0,ExtentRec.edition(A2) ;Set edition zero
MOVEA.L (A6),A2 ;Get globals pointer
MOVE.L D2,IACGlobals.AvailDependency(A2) ;It’s also the
available dependency
ADD.W #1,IACGlobals.ExtentCount(A2)
MOVE.W #noErr,D0 ;We succeeded!
AddDepExit
RTS
ENDPROC
;
; This routine adds the destination to a link that’s been
; started. If a passed-in slot_ID conflicts with an existing
; entry, it is re-assigned
DriverCompleteDependency PROC
IMPORT FindSlotID
CompDepRec RECORD 0
Func DS.W 1
docID DS.L 1
slot_ID DS.W 1
hatCheck DS.W 1
CompDepRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L CompDepRec.docID(A4),D0 ;Get the passed docID
BNE.S HaveDocID ;Go if we were passed docID
MOVE.L IACGlobals.AvailDependency(A2),D1 ;Get available
dependency
LEA IACGlobals.ExtentTable(A2),A2 ;Point to extent
records
MOVE.L ExtentRec.docID(A2,D1.L),D0 ;Get the docID
with CompDepRec
MOVE.L D0,docID(A4) ;Return to sender
HaveDocID
MOVE.W slot_ID(A4),D1 ;Did we get passed a slot_ID?
BNE.S ExistingSlotID ;Go get one if not
MOVE.L #DummyID,D3 ;Use dummy ID in slot for now
BRA.S NeedSlotID ;Go find slot to put it in
ExistingSlotID
MOVE.L (A6),A3
LEA IACGlobals.DocIDS(A3),A3 ;Pt to table of unique
SUBQ.W #1,D1 ;Adjust index to 0-based
LSL.W #2,D1 ;Convert to offset
CMPI.L #DummyID,0(A3,D1.W) ;Is passed slot_ID a dummy?
BNE.S CheckSlotID ;No, see if it’s OK
MOVE.L D0,0(A3,D1.W) ;Yes, replace with real thing
CheckSlotID
CMP.L 0(A3,D1.W),D0 ;DocIDs match?
BEQ.S HaveSlotID
NeedSlotID
BSR FindSlotID ;Get a valid slot ID
BLT.S CompDepExit ;failed...
HaveSlotID
MOVE.W D0,slot_ID(A4) ;Store slot_ID in caller’s
block
MOVE.L docID(A4),D3
endwith ;CompDepRec
MOVE.L D3,(A3,D1.W) ;Store docID
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L IACGlobals.AvailDependency(A2),D2 ;Get available
dependency index
LEA IACGlobals.ExtentTable(A2),A2 ;Point to extent
records
with ExtentRec
MOVE.L MstrInterestMsk(A2,D2.L),D3 ;Use register to
address all 32 bits
BSET D0,D3 ;Set the interested bit
MOVE.L D3,MstrInterestMsk(A2,D2.L) ;Back into table
MOVE.L EditionList(A2,D2.L),D1 ;Get the “Edition List
handle”
BGT.S WalkEditions0 ;If it’s a handle, deal with it
MOVE.W #NoSuchDep,D0 ;set error code
BEQ.S CompDepExit ;If it’s zero, we do nothing
MOVE.L #0,EditionList(A2,D2.L) ;Otherwise give it no
respect
BRA.S CompDepExit
endwith ;ExtentRec
WalkEditions0
MOVE.W ExtentRec.HatCheck(A2,D2.L),D3
MOVE.W D3,CompDepRec.hatCheck(A4) ;Return to caller
WalkEditions
MOVEA.L D1,A3 ;Copy the handle
MOVEA.L (A3),A3 ;Dereference the handle
with EditionHdr
MOVE.L InterestMask(A3),D3 ;So we can use 32-bit mode
BSET D0,D3 ;Set the appropriate bit
MOVE.L D3,InterestMask(A3)
MOVE.L NextEd(A3),D1 ;Get handle to next handle
endwith ;EditionHdr
BNE.S WalkEditions ;If there is one, deal with it
MOVE.W #noErr,D0 ;We succeeded!
CompDepExit
RTS
ENDPROC
;
; This routine severs a link and removes it from the tables
;
DriverRemoveDependency PROC
IMPORT FindExtentByDandHC,WipeExtentData
RemovDepRec RECORD 0
Func DS.W 1
docID DS.L 1
slotID DS.W 1
hatCheck DS.W 1
RemovDepRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L RemovDepRec.docID(A4),D0 ;Get the passed docID
MOVE.W RemovDepRec.hatCheck(A4),D1 ;Get the passed
hatCheck
JSR FindExtentByDandHC ;Find the extent
BNE.S RemovDepExit ;Use FEBAHDC’s errcode and scram.
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
MOVE.L ExtentRec.MstrInterestMsk(A2,D2.L),D3 ;Get
interest mask
MOVE.W RemovDepRec.slotID(A4),D4 ;Get the passed slotID
BCLR D4,D3 ;Declare this slot uninterested
BNE.S RemovTarget ;If bit WAS set, target was
doing the removing
RemovSource ;Otherwise it’s the source.
MOVEA.L ExtentRec.EditionList(A2,D2.L),A0 ;Stuff list
header into A0
JSR WipeExtentData ;Kill the extent’s data
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
with ExtentRec
MOVE.L #0,DocID(A2,D2.L) ;Clear DocID
MOVE.L #0,HatCheck(A2,D2.L) ;Clear HatCheck/Edition
MOVE.L #0,MstrInterestMsk(A2,D2.L) ;Clear
MstrInterestMsk
MOVE.L #0,EditionList(A2,D2.L) ;Clear EditionList
endwith ;ExtentRec
MOVE.W #noErr,D0 ;Set return code
BRA.S RemovDepCleanup
RemovTarget
TST.L D3 ;Does anybody care?
BEQ.S RemovSource ;If no, then go to sleep
MOVE.W #noErr,D0 ;Set return code
RemovDepCleanup
MOVE.W RemovDepRec.slotID(A4),D4 ;Get the passed slotID
SUBQ.W #1,D4
LSL.W #2,D4 ;Turn into offset
MOVEA.L (A6),A2 ;Dereference globals handle
with IACGlobals
SUBQ.W #1,ExtentCount(A2) ;One less extent in world
LEA DocIDs(A2),A2 ;Point to table of unique IDs
MOVE.L #0,0(A2,D4.W) ;Clear doc’s slot
endwith ;IACGlobals
RemovDepExit
RTS ;Bye-bye
ENDPROC
;
; This routine marks a link source as “available for
; completion”
;
DriverAvailableDependency PROC
IMPORT FindExtentByDandHC
AvailDepRec RECORD 0
Func DS.W 1
docID DS.L 1
hatCheck DS.W 1
AvailDepRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L AvailDepRec.docID(A4),D0 ;Get the passed docID
MOVE.W AvailDepRec.hatCheck(A4),D1 ;Get the passed
hatCheck
JSR FindExtentByDandHC ;Get dependancy if it
exists
BNE.S AvailDepExit ;If subr plotzed get out.
MOVEA.L (A6),A2
MOVE.L D2,IACGlobals.AvailDependency(A2) ;Otherwise, we
have a winner!
MOVE.W #noErr,D0 ;Set return code
AvailDepExit
RTS ;Bye-bye
ENDPROC
;
; This routine lets the client know what’s happening
;
DriverStatus PROC
StatusRec RECORD 0
Func DS.W 1
slotID DS.W 1
versID DS.W 1
docCount DS.W 1
extentCount DS.W 1
StatusRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.W StatusRec.slotID(A4),D0 ;Get slotID
MOVE.W #100,StatusRec.versID(A4) ;Report version as ‘100’
LEA IACGlobals.AvailDependency(A2),A3 ;Point past end
LEA IACGlobals.DocIDs(A2),A2 ;Point to extent table
MOVEQ #0,D3 ;Clear our counter
CountDoxLoop
TST.L 0(A2) ;Is the docID for real?
BGE.S CountDoxSkip ;If neg, then a modern date
ADDQ #1,D3 ;so add 1 to the count
CountDoxSkip
ADDA.L #4,A2 ;Else point to next record
CMPA.L A2,A3 ;Are we done?
BGT.S CountDoxLoop ;If not, look again
MOVE.W D3,StatusRec.docCount(A4) ;Report number of docs
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.DocIDs(A2),A3 ;Point past end
LEA IACGlobals.ExtentTable(A2),A2 ;Point to extent
table
MOVEQ #0,D3 ;Clear our counter
CountExtLoop
with ExtentRec
MOVE.L MstrInterestMsk(A2),D4 ;Fetch interest mask
BTST.L D0,D4 ;Is this slot interested?
BEQ.S CountExtSkip ;If not, then get next extent
MOVEA.L EditionList(A2),A0 ;Get EditionList HANDLE
endwith ;ExtentRec
FindExtLoop
MOVE.L A0,D4 ;valid address?
BEQ.S CountExtSkip ;No more editions, get out
MOVEA.L (A0),A0
MOVE.L EditionHdr.InterestMask(A0),D4
BTST.L D0,D4 ;Is this edition interested?
BEQ.S FindExtSkip ;No, so check next extent
ADDI.L #1,D3 ;Yes, add 1 and
BRA.S CountExtSkip ;Get out
FindExtSkip
MOVEA.L EditionHdr.NextEd(A0),A0 ;Move next EditionList
in
BRA.S FindExtLoop ;And test back there
CountExtSkip
ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next
record
CMPA.L A2,A3 ;Are we done?
BGT.S CountExtLoop ;If not, look again
MOVE.W D3,StatusRec.extentCount(A4) ;Report number of
extents
StatusExit
MOVE.W #0,D0 ;Indicate exit OK
RTS
ENDPROC
;
; This routine returns a count of active programs, links, etc. to the
client
;
DriverCensus PROC
Census1Rec RECORD 0
docID DS.L 1
edition DS.W 1
hatCheck DS.W 1
Census1RecSize EQU *
ENDR
CensusRec RECORD 0
Func DS.W 1
extentCount DS.W 1
Census1 DS.B Census1Rec
CensusRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L #CensusRec.Census1,D4 ;Offset to write for
output recs
LEA IACGlobals.DocIDs(A2),A3 ;Point past end
LEA IACGlobals.ExtentTable(A2),A2 ;Point to extent
table
MOVEQ #0,D3 ;Clear our counter
CensusLoop
TST.L ExtentRec.docID(A2) ;Check this extent’s docID
BEQ.S CensusSkip ;If wrong, skip to next
with ExtentRec
MOVE.L docID(A2),Census1Rec.docID(A4,D4.L) ;Move docID
MOVE.L Edition(A2),Census1Rec.edition(A4,D4.L) ;Move
edition
MOVE.L HatCheck(A2),Census1Rec.hatCheck(A4,D4.L) ;Move
hatCheck
endwith ;ExtentRec
ADDI.L #Census1Rec.Census1RecSize,D4 ;Move to next output
rec
ADDQ.W #1,D3
CensusSkip
ADDA.L #ExtentRec.ExtentSize,A2 ;Else point to next
record
CMPA.L A2,A3 ;Are we done?
BGT.S CensusLoop ;If not, look again
CensusExit
MOVE.W D3,CensusRec.extentCount(A4) ;Report extent count
MOVE.W #noErr,D0 ;Nothing is wrong
RTS ;Bye-bye
ENDPROC
;
; This routine is called by the client to write data to the
; driver
DriverWriteData PROC
IMPORT FindExtentByDandHC
WriteRec RECORD 0
Func DS.W 1
docID DS.L 1
hatCheck DS.W 1
edition DS.W 1
formatCount DS.W 1
theData DS.L 1
WriteRecSize EQU *
ENDR
FmtDataBlock RECORD 0
formatCode DS.L 1
dataSize DS.L 1
myData DS.L 1
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L WriteRec.docID(A4),D0 ;Get the passed docID
MOVE.W WriteRec.hatCheck(A4),D1 ;Get the passed hatCheck
JSR FindExtentByDandHC ;Get dependancy if it
exists
BEQ.S TableSetup ;Get going. Otherwise,
RTS ;subr plotzedleave w/o popping A1
TableSetup
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
MOVE.W WriteRec.formatCount(A4),D5 ;Get the passed
formatCount
EXT.L D5
ASL.L #2,D5 ;Make it an offset
MOVE.L A1,-(SP) ;preserve dCtlStorage Ptr
MOVEA.L WriteRec.theData(A4),A1 ;Get the passed
theData
MOVEA.L A1,A0
_HLock ;Freeze the user data
MOVEA.L (A1),A1 ;And put the pointer in A1
MOVE.L #EditionHdr.EditionSize,D0 ;How much global space
do we want?
ADD.L D5,D0 ;Add format entries
_NewHandle sys,clear ;Try to allocate it (sys
temporary)
BNE WriteDataFailed ;Couldn’t
MOVE.L A0,D7 ;preserve
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
with ExtentRec
ADDQ.W #1,Edition(A2,D2.L) ;Bump edition #
MOVEA.L EditionList(A2,D2.L),A3 ;Get handle to new
chain head
MOVE.L A0,EditionList(A2,D2.L) ;Point to new head of
chain
MOVE.L MstrInterestMsk(A2,D2.L),D6 ;Need to copy into
editionHdr
endwith ;ExtentRec
MOVEA.L D7,A0 ;Retrieve editionHdr handle
MOVEA.L (A0),A0 ;Hook list after new block
MOVE.L A3,EditionHdr.NextEd(A0) ;Set EditionList->next
MOVE.L D6,EditionHdr.InterestMask(A0) ;Remember who’s
interested
MOVE.W WriteRec.formatCount(A4),D6 ;Get the passed
formatCount
MOVE.W D6,EditionHdr.NumFormats(A0) ;set format count
MOVEA.L D7,A0
_HLock ;Freeze new block
MOVEA.L (A0),A3 ;Deref it
OncePerFormat
SUBI.W #4,D5 ;Knock down the format offset
MOVE.L FmtDataBlock.dataSize(A1),D0 ;Get this block’s
size
ADDI.L #4,D0 ;Add room for the fmtcode
MOVE.L D0,D6 ;Set up count
SUBQ.L #1,D6 ;Adjust for DBRA
_NewHandle sys,clear ;Try to allocate it
BNE.S WriteDataFailed ;Couldn’t
MOVE.L A0,D6 ;Save handle to IAC data copy
MOVE.L A1,D7 ;Save ptr to user data
MOVEA.L (A0),A0 ;Deref the IAC copy block
with FmtDataBlock
MOVE.L dataSize(A1),D0 ;Get this block’s size
MOVE.L formatCode(A1),(A0)+ ;Copy the format code
MOVEA.L A0,A1 ;Dest pointer for BlockMove
MOVEA.L D7,A0 ;Source pointer for BlockMove
ADDA.L #8,A0 ;Skip hdr info
_BlockMove
MOVE.L D7,A1
MOVE.L dataSize(A1),D0 ;Get this block’s size
endwith ;FmtDataBlock
ADDQ.L #8,D0 ;Allow for header
ADD.L D0,A1 ;Reset A1 to next format block
MOVE.L D6,A0 ;Get handle to IAC data copy
MOVE.L A0,EditionHdr.EdInstances(A3,D5.L) ;Record handle in
EditionList
TST.L D5 ;Are we done?
BNE.S OncePerFormat ;Do it again
MOVEA.L WriteRec.theData(A4),A0 ;Get the passed
theData
_HUnlock ;And unlock this puppy too
MOVE.L ExtentRec.Edition(A2,D2.L),D4
MOVE.L D4,WriteRec.edition(A4) ;Update the edition
MOVE.W #noErr,D0 ;We’re copacetic at this
point
WriteDataExit
MOVEA.L (SP)+,A1 ;restore dCtlStorage
RTS ;Bye-bye
WriteDataFailed
MOVE.W #WriteFailed,D0 ;So solly
BRA.S WriteDataExit
ENDPROC
;
; This routine is called by client to read data from the driver
;
DriverReadData PROC
IMPORT FindExtentByDandHC,IsFormatAvailable
ReadRec RECORD 0
Func DS.W 1
docID DS.L 1
slotID DS.W 1
hatCheck DS.W 1
edition DS.W 1
formatPref DS.L 3
formatCode DS.L 1
theData DS.L 1
ReadRecSize EQU *
ENDR
MOVEA.L (A6),A2 ;Dereference globals handle
MOVE.L ReadRec.docID(A4),D0 ;Get the passed docID
MOVE.W ReadRec.hatCheck(A4),D1 ;Get the passed hatCheck
JSR FindExtentByDandHC ;Get dependancy if it exists
BNE ReadDataExit ;If subr plotzed get out. Otherwise,
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A3 ;Get table base
MOVE.W ExtentRec.Edition(A3,D2.L),D4 ;Fetch latest stored
edition
CMPI.W #-1,D4 ;If edition not -1
BNE.S LinkStillValid ;Link is OK
MOVE.W #MissingLink,D0 ;Else link was invalidated
BRA ReadDataExit ;Go bye-bye
LinkStillValid
CMP.W ReadRec.edition(A4),D4 ;Has he seen my latest?
BLE.S FetchLatestEdition
MOVE.W #NoNewerEdition,D0 ;Yes, he has
BRA ReadDataExit
FetchLatestEdition
MOVEA.L ExtentRec.EditionList(A3,D2.L),A0 ;The handle for
latest ed’n
MOVEA.L (A0),A3 ;A3 -> latest ed’n
_HLock ;Freeze this puppy
LEA ReadRec.formatPref(A4),A2 ;Point to first format
JSR IsFormatAvailable ;Is format 1 ok?
BNE.S RenderMe ;If so, render it
LEA 4(A2),A2 ;Point to second format
TST.W (A2) ;If no format 2
BEQ ReadDataFailed
JSR IsFormatAvailable ;Is format 2 ok?
BNE.S RenderMe ;If so, render it
LEA 4(A2),A2 ;Point to third format
TST.W (A2) ;If no format 3
BEQ ReadDataFailed
JSR IsFormatAvailable ;Is format 3 ok?
BEQ ReadDataFailed ;If not, bail out
RenderMe
MOVE.L (A2),ReadRec.formatCode(A4) ;Set format code
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
MOVEA.L ExtentRec.EditionList(A2,D2.L),A0 ;The handle for latest
ed’n
MOVE.L A0,-(SP) ;Save for later
_HUnlock ;Dismiss it
MOVE.L D7,A0 ;Put render handle in A0
_GetHandleSize ;How big are we?
SUBQ.L #4,D0 ;Fudge size param for type code
MOVE.L D0,D6 ;Save size param
_HLock ;Freeze the EdInstance
MOVEA.L (A0),A3 ;Deref it
MOVEA.L ReadRec.theData(A4),A0 ;Get output handle
MOVE.L D6,D0 ;Get real size
_SetHandleSize ;Make enough room
MOVE.L A1,-(SP) ;Stack dCtlEntry
MOVEA.L (A0),A1 ;Destination
LEA 4(A3),A0 ;Source starts after formatCode
MOVE.L D6,D0 ;Reload real size
_BlockMove
MOVE.L (SP)+,A1 ;Retrieve dCtlEntry
MOVE.L D7,A0 ;Put render handle in A0
_HUnlock ;Let it go
;
; Now delete edition if no further interest
;
KillEditionLoop
TST.L (SP) ;Valid address?
BEQ.S ReadDataOK ;Exit if not..
MOVEA.L (SP),A0 ;Reload handle to edition header
MOVEA.L (A0),A3 ;A3 -> latest ed’n
with EditionHdr
MOVE.L InterestMask(A3),D5 ;Get interest mask
MOVE.W ReadRec.slotID(A4),D0 ;Get bit position
BCLR.L D0,D5 ;Clear interest bit
MOVE.L D5,InterestMask(A3) ;Save updated mask
BNE.S ReadDataOK ;Still interested parties, keep data
MOVE.W NumFormats(A3),D7 ;Loop limit
SUBQ.W #1,D7 ;Set up for DBRA
LEA EdInstances(A3),A2 ;Point to base of table
endwith ;EditionHdr
KillLoop
MOVEA.L (A2),A0 ;Handle to data
_DisposHandle ;Kill it
LEA 4(A2),A2 ;Next entry
DBRA D7,KillLoop
MOVE.L EditionHdr.NextEd(A3),D5 ;Save Link
MOVEA.L (SP),A0 ;Reload header handle
_DisposHandle ;Kill it
MOVE.L D5,(SP) ;Replace handle with link
BRA.S KillEditionLoop ;Try removing earlier edition
ReadDataOK
MOVEA.L (A6),A2 ;Dereference globals handle
LEA IACGlobals.ExtentTable(A2),A2 ;Get table base
MOVE.L (SP)+,ExtentRec.EditionList(A2,D2.L) ;Relink handle for
latest ed’n
MOVE.W #0,D0 ;Indicate exit OK
ReadDataExit
RTS
ReadDataFailed
MOVE.W #ReadFailed,D0 ;So solly
BRA.S ReadDataExit
ENDPROC
;
; Unused entry points that must be defined for the header’s jump
table
;
DriverCtl PROC
EXPORT DriverDone
DriverDone
MOVEQ #0,D0 ;Everything’s cool
RTS ;Return to sender
ENDPROC
;
;Many years later...the Subroutines!!
;
;Returns D0.W = slot_ID
; A3,D1.W pointing to available entry in docID list
FindSlotID PROC
MOVEA.L (A6),A2 ;Dereference global handle
LEA IACGlobals.docIDs(A2),A3 ;Point to list of docIDs
MOVEQ #-4,D1 ;Starting index of zero
MOVEQ #0,D3 ;Starting slot_ID
SlotIDLoop
ADD.W #4,D1 ;Bump to next index
ADD.W #1,D3 ;Bump counter
CMP.W #NumOfDocs+1,D3 ;Are we done yet?
BEQ.S SlotIDFail ;If so, we can’t create slot_ID
CMP.L (A3,D1.W),D0 ;Compare it to our docID
BEQ.S StuffSlotID ;Exit if we’re done
TST.L (A3,D1.W) ;See if open slot
BNE.S SlotIDLoop ;If not, try again
StuffSlotID
MOVE.W D3,D0 ;Move to result register
SlotExit
TST.W D0
RTS ;Return to sender
SlotIDFail
MOVE.W #NoMoreSlots,D0 ;Custom OSErr
BRA.S SlotExit
ENDPROC
;
;Looks for dep in extent table by matching docID and hatCheck
;ASSUMES THE FOLLOWING
; D0 = docID
; D1 = hatCheck
; A6 = globals handle [always]
;RETURNS
; D0 = noErr OR NoSuchDep
; D2 = desired offset
;We merrily destroy A2 and A3 during this routine
;
FindExtentByDandHC PROC
MOVEA.L (A6),A2
LEA IACGlobals.DocIDs(A2),A3 ;Point past end
LEA IACGlobals.ExtentTable(A2),A2 ;Point to extent table
MOVEQ #0,D2 ;Clear our offset
FEBDAHCLoop
CMP.L ExtentRec.docID(A2),D0 ;Check this extent’s docID
BNE.S SkipToNextExtent ;If wrong, skip to next
CMP.W ExtentRec.hatCheck(A2),D1 ;Check this extent’s docID
BNE.S SkipToNextExtent ;If wrong, skip to next
MOVE.W #noErr,D0 ;Nothing is wrong
BRA.S FEBDAHCExit ;Get out.
SkipToNextExtent
ADD.L #ExtentRec.ExtentSize,D2 ;Bump offset
ADDA.L #ExtentRec.ExtentSize,A2 ;Point to next record
CMPA.L A2,A3 ;Are we done?
BGT.S FEBDAHCLoop ;If not, look again
FEBDAHCFail
MOVE.W #NoSuchDep,D0 ;Return custom OSErr
FEBDAHCExit
TST.W D0
RTS ;Bye-bye
ENDPROC
;
;This routine wipes out all data and formats thereof for a single
extent
;ASSUMES
; A0 = current EditionList
;We merrily destroy A3,D0,and D5 (A2 restored from A6)
;RETURNS
; Nothing in particular
WipeExtentData PROC
with IACGlobals
with ExtentRec
MOVE.L A0,D0 ;Test handle to Edition List
BEQ.S DoneExtent ;If none, do nothing
MOVEA.L D0,A3 ;Keep Edition List handle
NextEdition
MOVEA.L A3,A0 ;Get Edition List handle
_HLock ;Lock the Edition List handle
MOVEA.L (A3),A0 ;Get pointer to Edition List
with EditionHdr
LEA EdInstances(A0),A2 ;Point to instance data
MOVE.W NumFormats(A0),D5 ;Get counter of formats
KillFormat
MOVEA.L (A2),A0 ;Get the handle to the data
_DisposHandle ;Get rid of it
LEA 4(A2),A2 ;Point to next instance
SUBQ.W #1,D5 ;Decrement the counter
BNE.S KillFormat ;And do it again
MOVEA.L (A3),A0 ;Dereference the Edition List handle
MOVE.L NextEd(A0),D5 ;Get handle to next Edition
MOVEA.L A3,A0 ;Restore handle
_HUnlock ;Unlock it
_DisposHandle ;Get rid of it
TST.L D5 ;Is there a next Edition?
BEQ.S DoneExtent ;If not, go to next extent
MOVEA.L D5,A3 ;Otherwise move the handle
BRA.S NextEdition ;And loop
endwith ;EditionHdr
endwith ;ExtentRec
endwith ;IACGlobals
DoneExtent
MOVEA.L (A6),A2 ;Restore
RTS
ENDPROC
;
;Looks for an dep in the extent table by matching the docID and
hatCheck
;ASSUMES THE FOLLOWING
; A2 = pointer to format(integer)
; A3 = pointer to Edition record
;RETURNS
; D7 = NIL OR handle to EdInstance
;We do not touch A2,A4 during this routine
;We merrily destroy A0,D4,D5 during this routine
;
IsFormatAvailable PROC
MOVE.W EditionHdr.NumFormats(A3),D5 ;Get number of
formats available
MOVE.L (A2),D4 ;Hold desired fmt code
SUBI.W #1,D5
LSL.W #2,D5 ;Make it an offset
MOVE.L #0,D7 ;Set return to NIL
FormatCheck
BLT.S FormatCheckDone ;D5 is now zero
MOVEA.L EditionHdr.EdInstances(A3,D5.W),A0 ;Move handle to
EdInstance
MOVE.L A0,D7 ;Save in case it’s good
MOVEA.L (A0),A0 ;Deref
CMP.L (A0),D4 ;Are the formats the same??
BEQ.S FormatCheckDone ;If so, leave
SUBI.W #4,D5 ;Check next one back
BRA.S FormatCheck
FormatCheckDone
TST.L D7 ;Set the condition code first
RTS
ENDPROC
END
{6}
Listing SAWSINIT.a
;**************************************************************
;*****The SAWS Inter-Application Communication Driver Loader
;*****Written with blazing speed 6-7/88 by Paul F. Snively
;***** With one HELL of a lotta help from Frank Alviani
;**************************************************************
;
;Modification History:
;6/19/88 First Draft--Paul F. Snively
;7/10/88 Hopefully last draft--this SHOULD work--
;Paul F. Snively
;8/21/88 Now searches for open slot from end of table--
;Frank Alviani
; (Algorithm from Pete Helme, Apple MACDTS)
;
;Basically what this puppy does is to assume that there’s a
;DRVR resource lying around that happens to be named “.IAC”
;and, if there is, it loads it into the
;System Heap and opens it. Simple, huh?
;
INCLUDE ‘Traps.a’
INCLUDE ‘QuickEqu.a’
INCLUDE ‘SysEqu.a’
INCLUDE ‘ToolEqu.a’
; STRING ASIS
successID EQU 128
failureID EQU 129
SAWSINIT: PROC
IMPORT ShowINIT
Frame RECORD 4,DECR
return DS.L 1
A6Link DS.L 1
theID DS.W 1
theType DS.L 1
name DS.B 256
fSize EQU *
ENDR
LINK A6,#Frame.fSize ;space for locals...
; Find an open slot in the driver table and load into that
MOVE.W UnitNtryCnt,D2 ;How many slots?
SUBQ.W #1,D2 ;Adjust
MOVE.W D2,D1 ;Set up offset
LSL.W #2,D1
MOVEA.L UTableBase,A0 ;Where’s the table?
SrchLoop
TST.L 0(A0,D1.W) ;Available?
BEQ.S GotSlot ;Yup...
SUBQ.L #4,D1 ;Drop Offset
SUBQ.W #1,D2 ;Drop slot ID
CMPI.L #39,D2 ;At bottom limit?
BGT.S SrchLoop
BRA.S BadNews ;No open slots in the inn
;Get resource by name
GotSlot
SUBQ.W #4,A7 ;Space for handle
MOVE.L #$44525652,-(A7) ;’DRVR’
PEA DriverName
_GetNamedResource
MOVE.W ResErr,D0 ;Get it?
BNE.S BadNews
MOVE.L (A7)+,D7 ;Was there enough memory?
BEQ.S BadNews
;Change ID to open slot
MOVE.L D7,-(A7)
PEA Frame.theID(A6) ;ID
PEA Frame.theType(A6) ;theType
PEA Frame.name(A6) ;name
_GetResInfo
MOVE.L D7,-(A7)
MOVE.W D2,-(A7)
PEA Frame.name(A6) ;name
_SetResInfo
;Open the driver!
CLR.W -(A7) ;Room for refnum
PEA DriverName ;Point to driver name
_OpenDeskAcc ;Open it
MOVE.W (A7)+,D1 ;Pop refnum
;Ensure Driver undisturbed
MOVE.L D7,-(A7)
_DetachResource
;Restore previous slot in file
MOVE.L #$44525652,-(A7) ;’DRVR’
PEA DriverName
_GetNamedResource
MOVE.L D7,-(A7)
MOVE.W Frame.theID(A6),D0
MOVE.W D0,-(A7)
MOVE.L #0,-(A7) ;leave alone name
_SetResInfo
MOVE.W #successID,-(SP) ;Stack success ICN# ID
ShowICON:
MOVE.W #-1,-(SP) ;Use standard pixel offset
MOVE.L (SP)+,D0 ;TEMPORARY! POP OFF PARMS
; JSR ShowINIT ;Tell user that driver installed
UNLK A6
RTS ;And that’s all, folks!
BadNews:
MOVE.W #failureID,-(SP) ;Tell user we failed
miserably
BRA.S ShowICON ;And leave
DriverName:
DC.B ‘.IAC’ ;The name of our driver
ENDPROC
END
{7}
Listing SAWSINIT.r
/* The Resource Description File for the IAC Driver
Frank Alviani -- 8/88
*/
include $$Shell(“hlxEtc”) “SAWSdrvr.lnk” ‘DRVR’ (31)
as ‘DRVR’ (31,”.IAC”,sysheap,nonpurgeable); /* get DRVR resource */
include $$Shell(“hlxEtc”) “SAWSINIT”; /* get INIT resource */
/* ICN# s will go here asap */